package avicit.bdp.dds.server.worker.processor;

import avicit.bdp.common.dto.CalculateEngineConf;
import avicit.bdp.common.service.service.CalculateEngineConfigService;
import avicit.bdp.common.utils.SpringApplicationContext;
import avicit.bdp.dds.dispatch.enums.ExecutionStatus;
import avicit.bdp.dds.dispatch.enums.TaskType;
import avicit.bdp.dds.api.service.TaskLogService;
import avicit.bdp.dds.common.Constants;
import avicit.bdp.dds.common.thread.ThreadUtils;
import avicit.bdp.dds.common.utils.FileUtils;
import avicit.bdp.dds.common.utils.OSUtils;
import avicit.bdp.dds.common.utils.Preconditions;
import avicit.bdp.dds.remote.command.Command;
import avicit.bdp.dds.remote.command.CommandType;
import avicit.bdp.dds.remote.command.TaskExecuteAckCommand;
import avicit.bdp.dds.remote.command.TaskExecuteRequestCommand;
import avicit.bdp.dds.remote.processor.NettyRequestProcessor;
import avicit.bdp.dds.remote.utils.FastJsonSerializer;
import avicit.bdp.dds.server.entity.TaskExecutionContext;
import avicit.bdp.dds.server.log.TaskLogDiscriminator;
import avicit.bdp.dds.server.worker.config.WorkerConfig;
import avicit.bdp.dds.server.worker.runner.TaskExecuteThread;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.sift.SiftingAppender;
import com.alibaba.fastjson2.JSONObject;
import io.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.concurrent.ExecutorService;

/**
 * worker request processor
 */
public class TaskExecuteProcessor implements NettyRequestProcessor {

    private final Logger logger = LoggerFactory.getLogger(TaskExecuteProcessor.class);


    /**
     * thread executor service
     */
    private final ExecutorService workerExecService;

    /**
     * worker config
     */
    private final WorkerConfig workerConfig;

    private final TaskLogService taskLogService;

    /**
     * task callback service
     */
    private final TaskCallbackService taskCallbackService;

    private CalculateEngineConfigService calculateEngineConfigService;

    public TaskExecuteProcessor() {
        this.taskCallbackService = SpringApplicationContext.getBean(TaskCallbackService.class);
        this.workerConfig = SpringApplicationContext.getBean(WorkerConfig.class);
        this.workerExecService = ThreadUtils.newDaemonFixedThreadExecutor("Worker-Execute-Thread", workerConfig.getWorkerExecThreads());
        this.calculateEngineConfigService = SpringApplicationContext.getBean(CalculateEngineConfigService.class);
        this.taskLogService = SpringApplicationContext.getBean(TaskLogService.class);
    }

    @Override
    public void process(Channel channel, Command command) {
        Preconditions.checkArgument(CommandType.TASK_EXECUTE_REQUEST == command.getType(),
                String.format("invalid command type : %s", command.getType()));

        TaskExecuteRequestCommand taskRequestCommand = FastJsonSerializer.deserialize(
                command.getBody(), TaskExecuteRequestCommand.class);

        logger.info("received command : {}", taskRequestCommand);

        String contextJson = taskRequestCommand.getTaskExecutionContext();

        TaskExecutionContext taskExecutionContext = JSONObject.parseObject(contextJson, TaskExecutionContext.class);
        taskExecutionContext.setHost(OSUtils.getHost() + ":" + workerConfig.getListenPort());

        // local execute path
        String execLocalPath = getExecLocalPath(taskExecutionContext);
        logger.info("task instance local execute path : {} ", execLocalPath);

        try {
            FileUtils.createWorkDirAndUserIfAbsent(execLocalPath, taskExecutionContext.getTenantCode());
        } catch (Exception ex) {
            logger.error(String.format("create execLocalPath : %s", execLocalPath), ex);
        }
        taskCallbackService.addRemoteChannel(taskExecutionContext.getTaskInstanceId(),
                new NettyRemoteChannel(channel, command.getOpaque()));

        try {
            this.doAck(taskExecutionContext);
        } catch (Exception e) {
            ThreadUtils.sleep(Constants.SLEEP_TIME_MILLIS);
            this.doAck(taskExecutionContext);
        }

        //设置计算引擎配置
        CalculateEngineConf  calculateEngine = calculateEngineConfigService.getSparkConfOrDefaultByDefinitionId(taskExecutionContext.getProcessDefineId());

        taskExecutionContext.setCalculateEngineConf(calculateEngine);
        //默认生成一条任务日志
        taskLogService.insertTaskLog(taskExecutionContext.getTaskInstanceId(), "");
        // submit task
        workerExecService.submit(new TaskExecuteThread(taskExecutionContext, taskCallbackService));
    }

    private void doAck(TaskExecutionContext taskExecutionContext) {
        // tell master that task is in executing
        TaskExecuteAckCommand ackCommand = buildAckCommand(taskExecutionContext);
        taskCallbackService.sendAck(taskExecutionContext.getTaskInstanceId(), ackCommand.convert2Command());
    }

    /**
     * get task log path
     *
     * @return log path
     */
    private String getTaskLogPath(TaskExecutionContext taskExecutionContext) {
        String baseLog = ((TaskLogDiscriminator) ((SiftingAppender) ((LoggerContext) LoggerFactory.getILoggerFactory())
                .getLogger("ROOT")
                .getAppender("TASKLOGFILE"))
                .getDiscriminator()).getLogBase();

        if (baseLog.startsWith(Constants.SINGLE_SLASH)) {
            return baseLog + Constants.SINGLE_SLASH +
                    taskExecutionContext.getProcessDefineId() + Constants.SINGLE_SLASH +
                    taskExecutionContext.getProcessInstanceId() + Constants.SINGLE_SLASH +
                    taskExecutionContext.getTaskInstanceId() + ".log";
        }

        return System.getProperty("user.dir") + Constants.SINGLE_SLASH +
                baseLog + Constants.SINGLE_SLASH +
                taskExecutionContext.getProcessDefineId() + Constants.SINGLE_SLASH +
                taskExecutionContext.getProcessInstanceId() + Constants.SINGLE_SLASH +
                taskExecutionContext.getTaskInstanceId() + ".log";
    }

    /**
     * build ack command
     *
     * @param taskExecutionContext taskExecutionContext
     * @return TaskExecuteAckCommand
     */
    private TaskExecuteAckCommand buildAckCommand(TaskExecutionContext taskExecutionContext) {
        TaskExecuteAckCommand ackCommand = new TaskExecuteAckCommand();
        ackCommand.setTaskInstanceId(taskExecutionContext.getTaskInstanceId());
        ackCommand.setStatus(ExecutionStatus.RUNNING_EXEUTION.getCode());
        ackCommand.setLogPath(getTaskLogPath(taskExecutionContext));
        ackCommand.setHost(taskExecutionContext.getHost());
        ackCommand.setStartTime(new Date());
        if (taskExecutionContext.getTaskType().equals(TaskType.SQL.name()) || taskExecutionContext.getTaskType().equals(TaskType.PROCEDURE.name())) {
            ackCommand.setExecutePath(null);
        } else {
            ackCommand.setExecutePath(taskExecutionContext.getExecutePath());
        }
        taskExecutionContext.setLogPath(ackCommand.getLogPath());
        return ackCommand;
    }

    /**
     * get execute local path
     *
     * @param taskExecutionContext taskExecutionContext
     * @return execute local path
     */
    private String getExecLocalPath(TaskExecutionContext taskExecutionContext) {
        return FileUtils.getProcessExecDir(taskExecutionContext.getProjectId(),
                taskExecutionContext.getProcessDefineId(),
                taskExecutionContext.getProcessInstanceId(),
                taskExecutionContext.getTaskInstanceId());
    }

}
